package com.dianping.localcache;

import com.dennytech.tac.utils.DefaultLockManager;
import com.dennytech.tac.utils.LockManager;
import com.dennytech.tac.utils.Log;

/**
 * the abstract cache class with memory cache and lock support
 * 
 * @author mmin
 * 
 */
public abstract class AbstractCacheManager<E> implements CacheManager<E> {
	private static final String TAG = AbstractCacheManager.class.getSimpleName();
	
	protected final CacheInfoManager cacheInfoManager;
	private final LockManager<String> lockManager;
	private boolean closed;

	/**
	 * return null if the data does not exists. if the data does not exists, the
	 * cache info will be deleted
	 */
	protected abstract E retrieveData(String url);

	/**
	 * return the bytes of the saved data, or -1 if failed
	 */
	protected abstract int saveData(String url, E data);

	/**
	 * do not to return result, the data must be deleted
	 */
	protected abstract void deleteData(String url);

	/**
	 * return null if expired. return it self if no changes.
	 */
	protected CacheInfo hitCache(CacheInfo ci) {
		return ci;
	}

	/**
	 * notify when new item is added or old one removed
	 */
	public abstract void notifyChanged(int oldSize, int newSize, int oldCount,
			int newCount);

	/**
	 * if the memCacheCapacity=0, then the memory cache is disabled
	 */
	public AbstractCacheManager(CacheInfoManager infoManager) {
		this.cacheInfoManager = infoManager;
		this.lockManager = new DefaultLockManager<String>();
	}

	/**
	 * the size of cache in bytes
	 */
	public int getSize() {
		return cacheInfoManager.getSize();
	}

	/**
	 * the item count of cache
	 */
	public int getCount() {
		return cacheInfoManager.getCount();
	}

	@Override
	public E get(String url) {
		if (closed) {
			Log.i(TAG, "get return null because closed = true");
			return null;
		}

		lockManager.lock(url);
		try {
			CacheInfo oci = cacheInfoManager.query(url);
			if (oci == null) {
				Log.i(TAG, "get return null because no cache info");
				return null;
			}
			CacheInfo ci = hitCache(oci);
			if (ci == null) {
				deleteData(url); // delete data first
				cacheInfoManager.delete(oci);
				
				Log.i(TAG, "get return null because hitCache return null cache info");
				return null;
			} else if (!ci.equals(oci)) {
				cacheInfoManager.delete(oci);
				cacheInfoManager.insert(ci);
			}
			E data = retrieveData(url);
			if (data == null) {
				cacheInfoManager.delete(ci);
				
				Log.i(TAG, "get return null because retrieveData return null");
				return null;
			} else {
				return data;
			}
		} finally {
			lockManager.unlock(url);
		}
	}

	@Override
	public boolean put(String url, E val) {
		if (closed) {
			Log.i(TAG, "put return false because closed = true");
			return false;
		}

		int oldSize = cacheInfoManager.getSize(), oldCount = cacheInfoManager.getCount();
		boolean exists = false;
		boolean r = false;
		lockManager.lock(url);
		try {
			CacheInfo ci = cacheInfoManager.query(url);
			if (ci != null) {
				Log.i(TAG, "put remove old data first");

				deleteData(ci.url); // delete data first
				cacheInfoManager.delete(ci);
				exists = true;
			}
			int bytes = saveData(url, val);
			if (bytes >= 0) {
				Log.i(TAG, "put saveData succeed, will insert CacheInfo");
				
				ci = new CacheInfo(url, bytes, System.currentTimeMillis());
				if (cacheInfoManager.insert(ci)) {
					Log.i(TAG, "put insert CacheInfo success");
					r = true;
				} else {
					Log.i(TAG, "put insert CacheInfo failed");
					deleteData(url);
					r = false;
				}
			}
			if (exists || r) {
				Log.i(TAG, "put notifyChanged because exists = " + exists + " r = " + r);
				
				notifyChanged(oldSize, cacheInfoManager.getSize(), oldCount,
					cacheInfoManager.getCount());
			}
			return r;
		} finally {
			lockManager.unlock(url);
		}
	}

	@Override
	public boolean remove(String url) {
		if (closed) {
			Log.i(TAG, "remove return false because closed = true");
			return false;
		}

		lockManager.lock(url);
		try {
			CacheInfo ci = cacheInfoManager.query(url);
			if (ci == null) {
				Log.i(TAG, "remove return false because no cache info");
				return false;
			}
			
			int oldSize = cacheInfoManager.getSize(), oldCount = cacheInfoManager.getCount();
			deleteData(url); // delete data first
			cacheInfoManager.delete(ci);
			notifyChanged(oldSize, cacheInfoManager.getSize(), oldCount,
				cacheInfoManager.getCount());
			return true;
		} finally {
			lockManager.unlock(url);
		}
	}

	/**
	 * ensure all read / write operation is finished before close
	 */
	public void close() {
		cacheInfoManager.close();
		closed = true;
	}

	public boolean isClosed() {
		return closed;
	}

	@Override
	public String toString() {
		return "AbstractCache, size=" + getSize() + ", count=" + getCount()
				+ "\n	cacheInfoManager: " + cacheInfoManager;
	}
}
